﻿#region Copyright (c) 2013-05, Olaf Kober <amarok.projects@gmail.com>
//================================================================================
//	Permission is hereby granted, free of charge, to any person obtaining a copy
//	of this software and associated documentation files (the "Software"), to deal
//	in the Software without restriction, including without limitation the rights
//	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//	copies of the Software, and to permit persons to whom the Software is
//	furnished to do so, subject to the following conditions:
//
//	The above copyright notice and this permission notice shall be included in
//	all copies or substantial portions of the Software.
//
//	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
//	THE SOFTWARE.
//================================================================================
#endregion

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using Amarok.Agents;


namespace Amarok.Presentation
{
	/// <summary>
	/// This type represents a base class for presentation-specific agent runtime implementations.
	/// </summary>
	public abstract class PresentationRuntime<TEnvironment> : Runtime<TEnvironment>,
		IWindowManager
		where TEnvironment : RuntimeEnvironment
	{
		// references
		private readonly Application mApplication;


		#region ++ IWindowManager Interface ++

		#region CheckAccess, VerifyAccess

		/// <summary>
		/// Determines whether the calling thread has access to the user interface.
		/// </summary>
		/// 
		/// <returns>
		/// True if the calling thread has access to the user interface; otherwise, False.</returns>
		public Boolean CheckAccess()
		{
			return mApplication.CheckAccess();
		}

		/// <summary>
		/// Enforces that the calling thread has access to the user interface.
		/// </summary>
		/// 
		/// <exception cref="InvalidOperationException">
		/// The calling thread does not have access to the user interface.</exception>
		public void VerifyAccess()
		{
			mApplication.VerifyAccess();
		}

		#endregion

		#region Invoke(Action)

		/// <summary>
		/// Executes the specified callback synchronously on the user interface thread.
		/// </summary>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Invoke(Action callback)
		{
			Verify.NotNull(callback, "callback");

			mApplication.Dispatcher.Invoke(
				callback);
		}

		/// <summary>
		/// Executes the specified callback synchronously on the user interface thread.
		/// </summary>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Invoke(Action callback, DispatcherPriority priority)
		{
			Verify.NotNull(callback, "callback");

			mApplication.Dispatcher.Invoke(
				callback,
				priority);
		}

		/// <summary>
		/// Executes the specified callback synchronously on the user interface thread.
		/// </summary>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// <param name="cancellationToken">
		/// An object that indicates whether to cancel the callback.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public void Invoke(Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
		{
			Verify.NotNull(callback, "callback");

			mApplication.Dispatcher.Invoke(
				callback,
				priority,
				cancellationToken);
		}

		#endregion

		#region InvokeAsync(Action)

		/// <summary>
		/// Executes the specified callback asynchronously on the user interface thread.
		/// </summary>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running callback. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task InvokeAsync(Action callback)
		{
			Verify.NotNull(callback, "callback");

#if NET45
			var operation = mApplication.Dispatcher.InvokeAsync(
				callback);

			return operation.Task;
#else
			var completionSource = new TaskCompletionSource<None>();

			var operation = mApplication.Dispatcher.BeginInvoke(
				new Action(() =>
					{
						try
						{
							callback();
							completionSource.TrySetResult(None.Instance);
						}
						catch (Exception exception)
						{
							completionSource.TrySetException(exception);
						}
					}));

			return completionSource.Task;
#endif
		}

		/// <summary>
		/// Executes the specified callback asynchronously on the user interface thread.
		/// </summary>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running callback. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task InvokeAsync(Action callback, DispatcherPriority priority)
		{
			Verify.NotNull(callback, "callback");

#if NET45
			var operation = mApplication.Dispatcher.InvokeAsync(
				callback,
				priority);

			return operation.Task;
#else
			var completionSource = new TaskCompletionSource<None>();

			var operation = mApplication.Dispatcher.BeginInvoke(
				new Action(() =>
				{
					try
					{
						callback();

						completionSource.TrySetResult(None.Instance);
					}
					catch (Exception exception)
					{
						completionSource.TrySetException(exception);
					}
				}),
				priority);

			return completionSource.Task;
#endif
		}

		/// <summary>
		/// Executes the specified callback asynchronously on the user interface thread.
		/// </summary>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// <param name="cancellationToken">
		/// An object that indicates whether to cancel the callback.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running callback. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task InvokeAsync(Action callback, DispatcherPriority priority, CancellationToken cancellationToken)
		{
			Verify.NotNull(callback, "callback");

#if NET45
			var operation = mApplication.Dispatcher.InvokeAsync(
				callback,
				priority,
				cancellationToken);

			return operation.Task;
#else
			var completionSource = new TaskCompletionSource<None>();

			var operation = mApplication.Dispatcher.BeginInvoke(
				new Action(() =>
				{
					try
					{
						callback();

						completionSource.TrySetResult(None.Instance);
					}
					catch (Exception exception)
					{
						completionSource.TrySetException(exception);
					}
				}),
				priority);

			cancellationToken.Register(() =>
				{
					operation.Abort();
					completionSource.TrySetCanceled();
				});

			return completionSource.Task;
#endif
		}

		#endregion

		#region Invoke(Func<T>)

		/// <summary>
		/// Executes the specified callback synchronously on the user interface thread.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of the return value returned from the specified callback.</typeparam>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// 
		/// <returns>
		/// The return value of the specified callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public T Invoke<T>(Func<T> callback)
		{
			Verify.NotNull(callback, "callback");

			return mApplication.Dispatcher.Invoke<T>(
				callback);
		}

		/// <summary>
		/// Executes the specified callback synchronously on the user interface thread.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of the return value returned from the specified callback.</typeparam>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// 
		/// <returns>
		/// The return value of the specified callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public T Invoke<T>(Func<T> callback, DispatcherPriority priority)
		{
			Verify.NotNull(callback, "callback");

			return mApplication.Dispatcher.Invoke<T>(
				callback,
				priority);
		}

		/// <summary>
		/// Executes the specified callback synchronously on the user interface thread.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of the return value returned from the specified callback.</typeparam>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// <param name="cancellationToken">
		/// An object that indicates whether to cancel the callback.</param>
		/// 
		/// <returns>
		/// The return value of the specified callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public T Invoke<T>(Func<T> callback, DispatcherPriority priority, CancellationToken cancellationToken)
		{
			Verify.NotNull(callback, "callback");

			return mApplication.Dispatcher.Invoke<T>(
				callback,
				priority,
				cancellationToken);
		}

		#endregion

		#region InvokeAsync(Action)

		/// <summary>
		/// Executes the specified callback asynchronously on the user interface thread.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of the return value returned from the specified callback.</typeparam>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running callback. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<T> InvokeAsync<T>(Func<T> callback)
		{
			Verify.NotNull(callback, "callback");

#if NET45
			var operation = mApplication.Dispatcher.InvokeAsync(
				callback);

			return operation.Task;
#else
			return mApplication.Dispatcher.InvokeAsync(
				callback);
#endif
		}

		/// <summary>
		/// Executes the specified callback asynchronously on the user interface thread.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of the return value returned from the specified callback.</typeparam>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running callback. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<T> InvokeAsync<T>(Func<T> callback, DispatcherPriority priority)
		{
			Verify.NotNull(callback, "callback");

#if NET45
			var operation = mApplication.Dispatcher.InvokeAsync(
				callback,
				priority);

			return operation.Task;
#else
			return mApplication.Dispatcher.InvokeAsync(
				callback,
				priority);
#endif
		}

		/// <summary>
		/// Executes the specified callback asynchronously on the user interface thread.
		/// </summary>
		/// 
		/// <typeparam name="T">
		/// The type of the return value returned from the specified callback.</typeparam>
		/// 
		/// <param name="callback">
		/// The callback to execute, which takes no arguments and does not return a value.</param>
		/// <param name="priority">
		/// The priority that determines in what order the specified callback is invoked relative to 
		/// other pending operations.</param>
		/// <param name="cancellationToken">
		/// An object that indicates whether to cancel the callback.</param>
		/// 
		/// <returns>
		/// A task that represents the asynchronously running callback. This task can be used to wait for completion, 
		/// to register a continuation and to access the state of the callback.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public Task<T> InvokeAsync<T>(Func<T> callback, DispatcherPriority priority, CancellationToken cancellationToken)
		{
			Verify.NotNull(callback, "callback");

#if NET45
			var operation = mApplication.Dispatcher.InvokeAsync(
				callback,
				priority,
				cancellationToken);

			return operation.Task;
#else
			return mApplication.Dispatcher.InvokeAsync(
				callback,
				priority,
				cancellationToken);
#endif
		}

		#endregion

		#region ShowWindow(WindowDescription)

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description. This method automatically 
		/// switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <param name="windowDescription">
		/// The window description that specifies which window should be shown.</param>
		/// <param name="environment">
		/// The agent environment that should be forwarded to the window's controller.</param>
		/// <param name="options">
		/// The agent options that should be forwarded to the window's controller.</param>
		/// 
		/// <returns>
		/// The method returns the <see cref="ICompletable"/> reference of the window description, which 
		/// can be used to tear down the window and associated controller.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public ICompletable ShowWindow(WindowDescription windowDescription, AgentEnvironment environment,
			AgentOptions options)
		{
			Verify.NotNull(windowDescription, "windowDescription");
			Verify.NotNull(environment, "environment");
			Verify.NotNull(options, "options");

			return this.Invoke(() =>
				{
					windowDescription.Create(environment, options);
					windowDescription.Window.Show();
					return windowDescription;
				});
		}

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description. This method automatically 
		/// switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <param name="windowDescription">
		/// The window description that specifies which window should be shown.</param>
		/// <param name="options">
		/// The agent options that should be forwarded to the window's controller.</param>
		/// 
		/// <returns>
		/// The method returns the <see cref="ICompletable"/> reference of the window description, which 
		/// can be used to tear down the window and associated controller.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public ICompletable ShowWindow(WindowDescription windowDescription, AgentOptions options)
		{
			return this.ShowWindow(
				windowDescription,
				base.Environment,
				options);
		}

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description. This method automatically 
		/// switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <returns>
		/// The method returns the <see cref="ICompletable"/> reference of the window description, which 
		/// can be used to tear down the window and associated controller.</returns>
		public ICompletable ShowWindow(WindowDescription windowDescription)
		{
			return this.ShowWindow(
				windowDescription,
				base.Environment,
				AgentOptions.Empty);
		}

		#endregion

		#region ShowWindow<T>()

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description. This method automatically 
		/// switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <param name="environment">
		/// The agent environment that should be forwarded to the window's controller.</param>
		/// <param name="options">
		/// The agent options that should be forwarded to the window's controller.</param>
		/// 
		/// <returns>
		/// The method returns the <see cref="ICompletable"/> reference of the window description, which 
		/// can be used to tear down the window and associated controller.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public ICompletable ShowWindow<TWindowDescription>(AgentEnvironment environment, AgentOptions options)
			where TWindowDescription : WindowDescription, new()
		{
			return this.ShowWindow(
				new TWindowDescription(),
				environment,
				options);
		}

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description. This method automatically 
		/// switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <param name="options">
		/// The agent options that should be forwarded to the window's controller.</param>
		/// 
		/// <returns>
		/// The method returns the <see cref="ICompletable"/> reference of the window description, which 
		/// can be used to tear down the window and associated controller.</returns>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		public ICompletable ShowWindow<TWindowDescription>(AgentOptions options)
			where TWindowDescription : WindowDescription, new()
		{
			return this.ShowWindow(
				new TWindowDescription(),
				options);
		}

		/// <summary>
		/// Shows a non-modal window specified by the supplied window description. This method automatically 
		/// switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <returns>
		/// The method returns the <see cref="ICompletable"/> reference of the window description, which 
		/// can be used to tear down the window and associated controller.</returns>
		public ICompletable ShowWindow<TWindowDescription>()
			where TWindowDescription : WindowDescription, new()
		{
			return this.ShowWindow(
				new TWindowDescription());
		}

		#endregion

		#region Exit()

		/// <summary>
		/// Shuts down the application. This method automatically switches into the user interface thread 
		/// to complete the request, if necessary.
		/// </summary>
		public void Exit()
		{
			this.InvokeAsync(() =>
				{
					mApplication.Shutdown();
				});
		}

		/// <summary>
		/// Shuts down an application that returns the specified exit code to the operating system. This 
		/// method automatically switches into the user interface thread to complete the request, if necessary.
		/// </summary>
		/// 
		/// <param name="exitCode">
		/// An integer exit code for an application. The default exit code is 0.</param>
		public void Exit(Int32 exitCode)
		{
			this.InvokeAsync(() =>
				{
					mApplication.Shutdown(exitCode);
				});
		}

		#endregion

		#endregion

		#region ++ Public Interface ++

		/// <summary>
		/// Gets a reference to the application object.
		/// </summary>
		public Application Application
		{
			get
			{
				return mApplication;
			}
		}

		#endregion

		#region ## Protected Interface ##

		/// <summary>
		/// Initializes a new instance.
		/// </summary>
		/// 
		/// <param name="application">
		/// A reference to the application object.</param>
		/// 
		/// <exception cref="ArgumentNullException">
		/// A null reference was passed to a method that did not accept it as a valid argument.</exception>
		protected PresentationRuntime(Application application)
		{
			Verify.NotNull(application, "application");
			mApplication = application;
		}

		#endregion

	}
}
